﻿// first_ffmpeg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <fstream>
using namespace std;

extern "C" { //指定函数是C语言函数，函数名不包含重载标注
//引用ffmpeg头文件
#include <libswscale/swscale.h>
}


//预处理指令导入库
#pragma comment(lib, "swscale.lib")
#define YUV_FILE "400_300_25.yuv"
#define RGBA_FILE "800_600_25.rgba"

int main()
{
    //cout << "first ffmpeg" << endl;
    //cout << avcodec_configuration() << endl;

    // ffmpeg -i test.mp4 -s 400x300 400_300_25.yuv
    // 400x300 YUV转RGBA 800x600 并存到文件
    int width = 400;
    int height = 300;
    int rgb_width = 800;
    int rgb_height = 600;

    //YUV420P 平面存储
    unsigned char* yuv[3] = { 0 };
    int yuv_linesize[3] = { width, width / 2, width / 2 };
    yuv[0] = new unsigned char[width * height];//Y
    yuv[1] = new unsigned char[width * height / 4];//U
    yuv[2] = new unsigned char[width * height / 4];//V

    //RGBA交叉存储
    unsigned char* rgba = new unsigned char[rgb_width * rgb_height * 4];
    int rgba_linesize = rgb_width * 4;

    ifstream ifs;
    ifs.open(YUV_FILE, ios::binary);
    if (!ifs) {
        cerr << "open " << YUV_FILE << " failed";
        return -1;
    }

    ofstream ofs;
    ofs.open(RGBA_FILE, ios::binary);
    if (!ofs)
    {
        cerr << "open " << RGBA_FILE << " failed";
        return -1;
    }

    SwsContext* yuv2rgb = nullptr;
    SwsContext* rgb2yuv = nullptr;
    
    //for (;;)
    for(int i=0; i< 10; i++)
    {
        //读取YUV帧
        ifs.read((char*)yuv[0], width * height);
        ifs.read((char*)yuv[1], width * height / 4);
        ifs.read((char*)yuv[2], width * height / 4);

        //gcount表示读到文件结尾，退出循环
        if (ifs.gcount() == 0) break;

        
        /*
        * sws_getCachedContext
        * YUV转RGBA 上下文创建和获取
        * 
        SwsContext NULL新创建，非NULL判断与现有参数是否一致
        一致直接返回，不一致先清理当前context，再次创建新的
        */
        yuv2rgb = sws_getCachedContext(
            yuv2rgb, //转换上下文
            width, height, //输入的宽高
            AV_PIX_FMT_YUV420P, //输入像素格式
            rgb_width, rgb_height, //输出的宽高
            AV_PIX_FMT_RGBA, //输出像素格式
            SWS_BILINEAR, //选择尺寸变化的算法，双线性插值
            0,0,0 //过滤器参数不使用
            );

        if (!yuv2rgb)
        {
            //排查一下参数，大概率参数写错了
            cerr << "sws_getCachedContext failed!" << endl;
            return -1;
        }

        unsigned char* data[1] = { rgba };
        int rgb_linesize[1] = { rgba_linesize };
        int re = sws_scale(yuv2rgb,
            yuv, //输入数据
            yuv_linesize, //输入数据的行字节数
            0,
            height, //输入高度
            data, //输出数据
            rgb_linesize //输出数据的行字节数
            );

        
        cout << re << " " << flush;
        ofs.write((char*)rgba, rgb_width * rgb_height * 4);

        //------for
    }

    //RGBA 转YUV420P
    ofs.close();
    ifs.close();

    //注意，要以二进制打开
    ifs.open(RGBA_FILE, ios::binary);
    
    for(;;)
    { 
        //读取RGBA帧
        ifs.read((char*)rgba, rgb_width * rgb_height * 4);

        //gcount表示读到文件结尾，退出循环
        if (ifs.gcount() == 0) break;

        rgb2yuv = sws_getCachedContext(
            rgb2yuv, //转换上下文
            rgb_width, rgb_height, //输入的宽高
            AV_PIX_FMT_RGBA, //输入像素格式
            width, height, //输出的宽高
            AV_PIX_FMT_YUV420P, //输出像素格式
            SWS_BILINEAR, //选择尺寸变化的算法，双线性插值
            0, 0, 0 //过滤器参数不使用
        );

        if (!rgb2yuv)
        {
            //排查一下参数，大概率参数写错了
            cerr << "sws_getCachedContext failed!" << endl;
            return -1;
        }

        unsigned char* data[1] = { rgba };
 
        int lines[1] = { rgba_linesize };
        int re = sws_scale(rgb2yuv,
            data,  //输入数据
            lines, //输入行字节数
            0,  
            rgb_height, //输入高度
            yuv,    //输出数据
            yuv_linesize    //输出行字节数
        );
        cout << "rgba2yuv_" << re << " " << flush;
    }

    delete yuv[0];
    delete yuv[1];
    delete yuv[2];
    delete [] rgba;
    
    return 0;
}

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件，或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来，若要再次打开此项目，请转到“文件”>“打开”>“项目”并选择 .sln 文件
